跳到主要内容

JavaScript 原型 和Object工具类

Object

参考资料 MDN Object 参考资料 JavaScript中的 Object 类型

ECMAScript 提供了两种创建创建对象的方式:构造函数创建和字面量创建。构造函数创建是指使用 new Object() 的方式创建,而字面量创建是指使用 {} 创建

// 使用构造函数创建对象
var obj = new Object({key:'a value'});

// 使用构造函数创建对象后,再添加属性
var obj1 = new Object();
obj1.key = 'a value';

// 使用字面量创建
var obj2 = {
key: 'a value'
};

// 字面量也可以使用以下形式
var obj3 = {};
obj3.key = 'a value';

在 JavaScript 中,几乎所有的对象都是 Object 类型的实例,它们都会从 Object.prototype 继承属性和方法。Object 构造函数为给定值创建一个对象包装器。Object 构造函数,会根据给定的参数创建对象,具体有以下情况:

  • 如果给定值是 nullundefined,将会创建并返回一个空对象
  • 如果传进去的是一个基本类型的值,则会构造其包装类型的对象
  • 如果传进去的是引用类型的值,仍然会返回这个值,经他们复制的变量保有和源对象相同的引用地址

Object 的实例属性/方法

Object 是所有对象的父对象,它的实例属性与方法也同时存在于所有的对象的实例中

// 值为 1
Object.length

// 可以为所有 Object 类型的对象添加属性。
Object.prototype

// 例如
Shape.prototype.move = function(x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};

// 对象的构造函数。前面使用 new 创建对象时,其构造函数是 Object()
Object.prototype.constructor

// 返回一个布尔值。用于检查给定的属性是实例属性,而非从原型链继承的属性。
Object.prototype.hasOwnProperty(propName)
var o = new Object();
o.age = 18;
o.hasOwnProperty('age'); // 返回 true
o.hasOwnProperty('toString'); // 返回 false,toString 继承自 Object

// 返回一个布尔值。用于检查给定的对象是否在原型链上
Object.prototype.isPrototypeOf(object)
// 判断对象属性是否是可枚举的
Object.prototype.propertyIsEnumerable(propName)
// 返回对象的字符串表示,即:调用 Object.prototype.toString()
Object.prototype.toLocaleString()
// 返回对象的字符串表示
Object.prototype.toString()
// 返回对象的原始值
Object.prototype.valueOf()

Object 对象的拷贝

参考资料 JavaScript 对象属性拷贝-Object.assign()

Object 对象提供了一个复制对象属性的方法:Object.assign(),该方法会复制一或多个源对象的属性(可枚举)到目标对象,并返回目标对象。

// target,目标对象
// sources,源对象,可以一个或多个
// 返回值:复制 sources 属性后的 target
Object.assign(target, ...sources)

Object.assign() 会把一个或多源对象的可枚举(可访问)属性复制给目标对象。

使用 assign() 方法浅拷贝一个对象:

var obj = {domain:'temp.com'};
var copy = Object.assign({}, obj);
console.log(copy); // domain:'temp.com'

使用 assign() 方法合并多个对象:

var obj1 = {domain:'temp.com'};
var obj2 = {name: '测试'};
var obj = Object.assign(obj1, obj2);
console.log(obj); //{ domain: 'temp.com', name: '测试' }

Object 对象继承和创建

参考资料 MDN Object.create() 参考资料 MDN Object.defineProperties() 参考资料 Object.create()方法实现对象继承与创建新的JavaScript对象

Object.create() 方法是 ECMAScript 5中新增的方法,这个方法用于创建一个新对象。被创建的对象会继承另一个对象的原型,在创建新对象时还可以指定一些属性。

Object.create(proto [, propertiesObject])
  • proto,{object},要继承的原型
  • propertiesObject,{object},可选参数,为新创建对象指定的属性对象。该参数对象是一组属性和值,对象的属性名将会是新创建的对象的属性名,属性值是属性描述符。该对象可能会包含以下值:
    • configurable,表示新创建的对象是否是可配置的,即:对象的属性是否可以被删除或修改,默认 false
    • enumerable,对象属性是否可枚举的,即:是否可以 for of 枚举,默认 false
    • value,对象的值,可以是任何合法的 JavaScript 的值,如:number、object、function 等,默认 undefined
    • writable,对象是否可写,是否或以为对象添加新属性,默认 false
    • get,对象 Getter 函数,默认 undefined
    • set,对象 Setter 函数,默认 undefined

使用 Object.create() 方法创建对象

let o = Object.create(Object.prototype, {
// foo会成为所创建对象的数据属性
foo: {
writable:true,
configurable:true,
value: "hello"
},
// bar会成为所创建对象的访问器属性
bar: {
configurable: false,
get: function() { return 10 },
set: function(value) {
console.log("Setting `o.bar` to", value);
}
}
});

console.log(o); // {foo: "hello", bar: 10}

o.foo = "this new Str";
console.log(o.foo); // this new Str

使用 Object.create() 方法使用类式继承:

// 基类
function Father() {
this.name = 'Site';
this.domain = 'domain';
}

Father.prototype.create = function(name, domain) {
this.name = name;
this.domain = domain;
};

// 子类
function Sub() {
Father.call(this); //调用基类的构造函数
}

// 继承父类
Sub.prototype = Object.create(Father.prototype);

// 创建类实例
var sub = new Sub();

sub instanceof Father; // true
sub instanceof Sub; // true

sub.create('测试', 'temp.com');
sub.name; // '测试'
sub.domain; // 'temp.com'

defineProperty 定义对象

参考资料 MDN Object.defineProperty() 参考资料 深入浅出Object.defineProperty()

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。(该方法允许精确地添加或修改对象的属性)

// 语法
Object.defineProperty(obj, prop, descriptor)
  • obj 需要定义属性的当前对象
  • prop 当前需要定义的属性名
  • descriptor 属性描述符

使用实例

const object1 = {};

Object.defineProperty(object1, 'property1', {
value: 42,
writable: false // 是否可以改变
});

object1.property1 = 77;
// throws an error in strict mode

console.log(object1.property1);
// expected output: 42

可以使用这个方法给对象添加 getter、setter 方法(getsetvaluewritable 不能混用,因为有了getset 所以 writable 失效了)

let Person = {}
let temp = null
Object.defineProperty(Person, 'name', {
get: function () {
return temp
},
set: function (val) {
temp = val
}
})

Object 其它静态方法

// 给对象添加多个属性并分别指定它们的配置。
Object.defineProperties()
// 返回给定对象自身可枚举属性的 [key, value] 数组。
Object.entries()
// 冻结对象:其他代码不能删除或更改任何属性。
Object.freeze()
// 返回对象指定的属性配置。
Object.getOwnPropertyDescriptor()
// 返回一个数组,它包含了指定对象所有的可枚举或不可枚举的属性名。
Object.getOwnPropertyNames()
// 返回一个数组,它包含了指定对象自身所有的符号属性。
Object.getOwnPropertySymbols()
// 返回指定对象的原型对象。
Object.getPrototypeOf()
// 比较两个值是否相同。所有 NaN 值都相等(这与==和===不同)。
Object.is()
// 判断对象是否可扩展。
Object.isExtensible()
// 判断对象是否已经冻结。
Object.isFrozen()
// 判断对象是否已经密封。
Object.isSealed()
// 返回一个包含所有给定对象自身可枚举属性名称的数组。
Object.keys()
// 防止对象的任何扩展。
Object.preventExtensions()
// 防止其他代码删除对象的属性。
Object.seal()
// 设置对象的原型(即内部 [[Prototype]] 属性)。
Object.setPrototypeOf()
// 返回给定对象自身可枚举值的数组。
Object.values()

__proto__ 原型

参考资料 MDN 继承与原型链 参考资料 JavaScript 最大的秘密

JS 不使用 class,它用的是 object链,即原型链模式

当谈到继承时,JavaScript 只有一种结构:对象,虽然它没有类但是每个实例对象( object )都有一个私有属性 __proto__,指向它的构造函数的原型对象(prototype )。

该原型对象也有一个自己的原型对象 __proto__ ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。(在 ES2015/ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 仍然是基于原型的)

image.png

如下所示:

var animal = {
name: "animal",
eat: function() {
console.log(this.name + "is eating");
}
}

var dog = {
name: "dog",
__proto__: animal // 指向 animal
}

var cat = {
name: "cat",
__proto__: animal // 指向 animal
}

dog.eat(); // dog is eating
cat.eat(); // cat is eating

如上,由于对象并不与类关联,可以随意的给它增加属性

几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

尽管这种原型继承通常被认为是 JavaScript 的弱点之一,但是原型继承模型本身实际上比经典模型更强大。例如,在原型模型的基础上构建经典模型相当简单。

基于原型链的继承

再来一个继承的例子

//定义一个模板
let user = {
name:'',
age:20,
sex:'male',
run:function () {
console.log(this.name + " run...");
}
}

let alsritter = {
name: "alsritter"
}
//继承上面那个模板
alsritter.__proto__ = user;

//继承模板之后就可以使用那个run方法了
alsritter.run()

new 关键字

参考资料 javascript中new关键字详解

由上面可以知道,JS 的继承都是基于原型的,但是常写 JavaScript 就会发现,里面还是有很多 new 关键字的,但是这个 new 的不是 Class 而是一个 function,它们是什么原理呢?

其实这个 function 是一个构造函数

function Student(name) {
this.name = name,
this.sayHello = function() {
console.log("Hi, I'm " + this.name);
}
}

andy = new Student("andy");
lisa = new Student("lisa");

andy.sayHello(); // Hi, I'm andy
lisa.sayHello(); // Hi, I'm lisa

注意,一般都将这种需要实例化的构造函数以大写开头

prototype 属性

但是上面 new 对象也不是没有缺点,例如上面每个新建的对象下面都有一个 sayHello 函数

image6fcd93ee92671312.png

所以这种时候就可以创建一个原型对象,让 andy 和 lisa 这些从 Student 创建起来的对象指向这个原型对象就行了,但是这里也有一个问题,上面只有构造函数 Student,在哪里将这些对象的 __proto__ 指向原型对象呢?

所以官方提供了一个 prototype 属性,使用这个属性后每次创建对象,JavaScript 就会自动把原型链给建立起来

function Student(name) {
this.name = name;
}

Student.prototype = {
sayHello: function() {
console.log("Hi, I'm " + this.name);
}
}

andy = new Student("andy");
lisa = new Student("lisa");

andy.sayHello(); // Hi, I'm andy
lisa.sayHello(); // Hi, I'm lisa

image397285531d0310ba.png

call 和 apply 改变 this 指向

参考资料 【优雅代码】深入浅出 妙用Javascript中apply、call、bind

在 JavaScript 中,callapply 都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内部 this 的指向。

JavaScript 的一大特点是,函数存在「定义时上下文」和「运行时上下文」以及「上下文是可以改变的」这样的概念。

function fruits() {}

fruits.prototype = {
color: "red",
say: function() {
console.log("My color is " + this.color);
}
}

var apple = new fruits;
apple.say(); //My color is red

但是如果有一个对象 banana= {color : "yellow"},我们不想对它重新定义 say 方法,那么我们可以通过 callapply 用 apple 的 say 方法:

banana = {
color: "yellow"
}

apple.say.call(banana); //My color is yellow
apple.say.apply(banana); //My color is yellow

可以看出 callapply 是为了动态改变 this 而出现的,当一个 Object 没有某个方法(例如这里的 banana 没有 say 方法),但是其他的有(这里的 apple 有 say 方法),我们可以借助 callapply 用其它对象的方法来操作。

apply、call 的作用完全一样,只是接受参数的方式不太一样

var func = function(arg1, arg2) {

};

func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2])